package org.awa.demo.opencv.task;

import cn.hutool.core.util.RandomUtil;
import lombok.extern.slf4j.Slf4j;
import org.awa.demo.opencv.bean.NoiseSource;
import org.awa.demo.opencv.gltf.SoundUtil;
import org.awa.demo.opencv.util.TicketUtil;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.math.Vector3D;
import wcontour.Contour;
import wcontour.Interpolate;
import wcontour.global.*;
import wcontour.global.Polygon;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
public class DemoTask implements Runnable{

    @Override
    public void run() {
        //参数
        String polygon = "MULTIPOLYGON (((0 0, 0 200, 200 200, 200 120, 100 100, 100 0, 0 0)))";
        int altitude = 4;
        int noiseSize = 20;
        double sideLen = 0.01;
        double _R = 332.1520985690918;
        double[] dataInterval = new double[]{
                95,100,105,110
        };

        boolean debugData = false;

        TicketUtil ticketUtil = new TicketUtil();

        try{
            Geometry range = SoundUtil.createGeometryFromWKT(polygon);

            //随机生成噪声源
            List<NoiseSource> noiseSourceList = new ArrayList<>();
            for (int i = 0; i < noiseSize; i++) {
                NoiseSource.NoiseSourceBuilder builder = NoiseSource.builder();
                NoiseSource noiseSource = builder.Q(randomQ()).R(_R).location(randomVector(range,altitude)).build();
                noiseSource.calLw(randomLeqt(),randomVector(range,altitude));
                noiseSourceList.add(noiseSource);

                if(debugData){
                    log.info("噪声源{}:[{} {}]Lw:{}",i,noiseSource.getLocation().getX(),noiseSource.getLocation().getY(),noiseSource.getLw());
                }

            }
            ticketUtil.ticket("噪声源创建完毕");

            //获取轮廓
            Envelope envelopeInternal = range.getEnvelopeInternal();
            double minX = envelopeInternal.getMinX();
            double maxX = envelopeInternal.getMaxX();
            double minY = envelopeInternal.getMinY();
            double maxY = envelopeInternal.getMaxY();


            int width = (int) Math.ceil((maxX - minX) / sideLen);
            int height = (int) Math.ceil((maxY - minY) / sideLen);
            double[] _x = new double[width];
            double[] _y = new double[height];
            if(debugData){
                log.info("Envelope范围:[{} {},{} {}],width:{},height:{}",minX,minY,maxX,maxY,width,height);
            }

            ticketUtil.ticket("范围计算完毕");

            //网格化
            Interpolate.createGridXY_Num(minX,minY,maxX,maxY,_x,_y);
            if(debugData){
                log.info("x数组:" + Arrays.stream(_x).mapToObj(String::valueOf).collect(Collectors.joining(",")));
                log.info("y数组:" + Arrays.stream(_y).mapToObj(String::valueOf).collect(Collectors.joining(",")));
            }
            ticketUtil.ticket("网格划分完毕");

            double[][] _gridData = new double[width][height];

            double[][] trainData = new double[noiseSourceList.size()][3];
            for (int i = 0; i < noiseSourceList.size(); i++) {
                NoiseSource noiseSource = noiseSourceList.get(i);
                trainData[i][0] = noiseSource.getLocation().getX();
                trainData[i][1] = noiseSource.getLocation().getY();
                trainData[i][2] = noiseSource.getLw();

                if(debugData){
                    log.info("trainData{}:[{} {},{}]",i,trainData[i][0],trainData[i][1],trainData[i][2]);
                }
            }

            ticketUtil.ticket("噪声点转换完毕");

            //IDW插值
            _gridData = Interpolate.interpolation_IDW_Neighbor(trainData,_x,_y,2,-99);

            ticketUtil.ticket("IDW插值完毕");

            if(debugData){
                log.info("---------------------- grid data --------------------");
                for (int i = 0; i < _gridData.length; i++) {
                    log.info(Arrays.stream(_gridData[i]).mapToObj(String::valueOf).collect(Collectors.joining("\t")));
                }
                log.info("---------------------- grid data end --------------------");
            }
            int[][] s1 = new int[_gridData.length][_gridData[0].length];

            //提取轮廓线
            List<Border> borders = Contour.tracingBorders(_gridData, _x, _y, s1, -99);
            ticketUtil.ticket("轮廓线提取完毕");

            if(debugData){
                log.info("---------------------- s1 --------------------");
                for (int i = 0; i < _gridData.length; i++) {
                    log.info(Arrays.stream(s1[i]).mapToObj(String::valueOf).collect(Collectors.joining("\t")));
                }
                log.info("---------------------- s1 end --------------------");

                log.info("---------------------- Border --------------------");
                for (int i = 0; i < borders.size(); i++) {
                    Border border = borders.get(i);
                    List<Geometry> geometryByBorder = getGeometryByBorder(border);
                    for (int j = 0; j < geometryByBorder.size(); j++) {
                        Geometry geometry = geometryByBorder.get(j);
                        log.info("border{}:{}",i,geometry.toText());
                    }
                }
                log.info("---------------------- Border end --------------------");

                log.info("data interval : {}",Arrays.stream(dataInterval).mapToObj(String::valueOf).collect(Collectors.joining("\t")));
            }

            //生成等值线
            List<PolyLine> polyLines = Contour.tracingContourLines(_gridData, _x, _y, dataInterval.length, dataInterval, -99, borders, s1);

            ticketUtil.ticket("等值线计算完毕-"+polyLines.size());

            if(debugData){
                log.info("---------------------- 等值线 --------------------");
                for (int i = 0; i < polyLines.size(); i++) {
                    PolyLine polyLine = polyLines.get(i);
                    Geometry geometryByPolyLine = getGeometryByPolyLine(polyLine);
                    log.info(geometryByPolyLine.toText());
                }
                log.info("---------------------- 等值线 end --------------------");
            }

            //平滑
            polyLines = Contour.smoothLines(polyLines);

            ticketUtil.ticket("等值线平滑完毕-"+polyLines.size());
            if(debugData){
                log.info("---------------------- 平滑后的等值线 --------------------");
                for (int i = 0; i < polyLines.size(); i++) {
                    PolyLine polyLine = polyLines.get(i);
                    Geometry geometryByPolyLine = getGeometryByPolyLine(polyLine);
                    log.info(geometryByPolyLine.toText());
                }
                log.info("---------------------- 平滑后的等值线 end --------------------");
            }

            //生成等值面
            List<Polygon> polygons = Contour.tracingPolygons(_gridData, polyLines, borders, dataInterval);
            ticketUtil.ticket("等值面生成完毕-"+polyLines.size());
            //分离出元素
            List<DengZhiXianBean> result = polygons.stream().map(item -> {
                DengZhiXianBean bean = new DengZhiXianBean();
                bean.setGeometry(getGeometryByPolygon(item));
                bean.setLowValue(item.LowValue);
                bean.setHighValue(item.HighValue);
                return bean;
            }).collect(Collectors.toList());

            result.stream().forEach(bean -> {
                log.info("---------------------- 等值面 ----------------------------");
                bean.getGeometry().toText();
                log.info("geometry:"+bean.getGeometry().toText());
                log.info("high value:"+bean.getHighValue());
                log.info("low value:"+bean.getLowValue());
                log.info("--------------------- 等值面 end ----------------------------");
            });

            ticketUtil.ticket("等值面打印完毕");

//
//            //栅格化
//            List<Geometry> gridGeometryList = new ArrayList<>();
//
//            SimpleFeatureSource grid = Grids.createSquareGrid(new ReferencedEnvelope(
//                    envelopeInternal.getMinX(),envelopeInternal.getMaxX(),
//                    envelopeInternal.getMinY(),envelopeInternal.getMaxY(),
//                    DefaultGeographicCRS.WGS84),sideLen);//WGS84 说是没用，看看效果再说
//            SimpleFeatureIterator iterator = grid.getFeatures().features();
//            while (iterator.hasNext()){
//                SimpleFeature feature = iterator.next();
//                Geometry geometry = (Geometry) feature.getDefaultGeometry();
//                if(geometry.intersects(range)){
//                    //判断相交，获取质心坐标，以及交线长度，存入水平栅格数组。水平栅格数组与高度配合形成空间栅格数组
//                    log.info("生成栅格:{}",geometry.toText());
//                    gridGeometryList.add(geometry);
//                }
//            }
//
//            //

        }catch (Exception e){
            e.printStackTrace();
            log.error("exception happened: " + e.getMessage(),e);
        }finally {
            ticketUtil.close();
            log.info("over");
        }
    }

    private Integer randomQ(){
        int type = RandomUtil.randomInt(1, 4);
        switch (type){
            case 1:
                return 1;
            case 2:
                return 2;
            case 3:
                return 4;
            case 4:
                return 8;
            default:
                return 1;
        }
    }

    private Double randomLeqt() {
        return BigDecimal.valueOf(RandomUtil.randomInt(500, 900)).divide(BigDecimal.valueOf(10), 1, RoundingMode.HALF_DOWN).doubleValue();
    }

    private Vector3D randomVector(Geometry range, int height){
        GeometryFactory geometryFactory = new GeometryFactory();
        Envelope envelope = range.getEnvelopeInternal();
        while (true){
            double x = envelope.getMinX() + RandomUtil.randomDouble(1) * envelope.getWidth();
            double y = envelope.getMinY() + RandomUtil.randomDouble(1) * envelope.getHeight();

            Point point = geometryFactory.createPoint(new Coordinate(x, y));
            if(range.covers(point)){
                return new Vector3D(point.getX(), point.getY(), RandomUtil.randomDouble(0, height));
            }
        }
    }

    private List<Geometry> getGeometryByBorder(Border border){
        GeometryFactory geometryFactory = new GeometryFactory();
        List<Geometry> result = new ArrayList<>();

        List<BorderLine> lineList = border.LineList;
        for (BorderLine line : lineList) {
            Coordinate[] outCoorArr = line.pointList.parallelStream().map(point ->{
                return new Coordinate(point.X,point.Y);
            }).toArray(Coordinate[]::new);
            LineString lineString = geometryFactory.createLineString(outCoorArr);
            result.add(lineString);
        }

        return result;
    }

    private Geometry getGeometryByPolygon(Polygon polygon){
        GeometryFactory geometryFactory = new GeometryFactory();

        PolyLine outLine = polygon.OutLine;
        Coordinate[] outCoorArr = outLine.PointList.parallelStream().map(point ->{
            return new Coordinate(point.X,point.Y);
        }).toArray(Coordinate[]::new);
        LinearRing outRing = geometryFactory.createLinearRing(outCoorArr);

        if(polygon.HasHoles()){
            List<PolyLine> holeLines = polygon.HoleLines;
            LinearRing[] holeRingArr = new LinearRing[holeLines.size()];

            for (int i = 0; i < holeLines.size(); i++) {
                PolyLine holdPolyLine = holeLines.get(i);
                Coordinate[] holdCoorArr = holdPolyLine.PointList.parallelStream().map(point -> {
                    return new Coordinate(point.X, point.Y);
                }).toArray(Coordinate[]::new);
                holeRingArr[i] = geometryFactory.createLinearRing(holdCoorArr);
            }

            return geometryFactory.createPolygon(outRing,holeRingArr);
        }else{
            return geometryFactory.createPolygon(outRing);
        }
    }

    private Geometry getGeometryByPolyLine(PolyLine polyLine){
        GeometryFactory geometryFactory = new GeometryFactory();

        Coordinate[] outCoorArr = polyLine.PointList.parallelStream().map(point ->{
            return new Coordinate(point.X,point.Y);
        }).toArray(Coordinate[]::new);
        return geometryFactory.createLineString(outCoorArr);
    }
}
